package com.feriki.msync.utils;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 采用snowflake算法产生全局唯一ID
 *
 */
@Slf4j
public class IDGenerator {
    
    private volatile long workerId;
    public void setWorkerId(long workerId) {
        this.workerId = workerId;
    }
    private volatile long datacenterId;
    public void setDatacenterId(long datacenterId) {
        this.datacenterId = datacenterId;
    }
    private long sequence = 0L;
 
    // 2016-01-01
    private long twepoch = 1451577600000L;
 
    private long workerIdBits = 8L;
    private long datacenterIdBits = 8L;
    private long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private long sequenceBits = 6L;
 
    private long workerIdShift = sequenceBits;
    private long datacenterIdShift = sequenceBits + workerIdBits;
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private long sequenceMask = -1L ^ (-1L << sequenceBits);
 
    private long lastTimestamp = -1L;

    private static final IDGenerator IDGENERATOR = new IDGenerator();

    //时钟是否回拨标记
    private AtomicBoolean clockMovedBackward = new AtomicBoolean(false);

    /**
     * 使用随机值作为workerId和centerId<br>
     * <b>注意</b>：同一台服务器中若在一个或多个进程创建了不止一个IDGenerator对象时，
     * 这些对象同一时刻生成的id会相同<br>
     * <b>注意</b>：若想保证同一台服务器多个进程的IDGenerator生成的id互不相同，
     * 请使用 IDGenerator(long workerId, long datacenterId)构造方法
     */
    public IDGenerator() {
        this.workerId = (int) (Math.random() * maxWorkerId);
        this.datacenterId = (int) (Math.random() * maxDatacenterId);
    }
    
    /**
     * 任意服务器上的任意两个进程，只要workderId和datacenterId任意一个不相同，
     * 则同一时刻这两个进程生成的id就会不同
     * @param workerId
     * @param datacenterId
     */
    public IDGenerator(long workerId, long datacenterId) {
        // sanity check for workerId
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    /**适配时钟回拨的情形：若发现时钟回拨，则在单位时间内雪花序列递进，若序列已经最大，则单位时间递进。直到最大时间戳lastTimestamp小于当前系统时间后再恢复为系统当前时间。
     * <p>
     * 时钟回拨定义:获取到系统当前时间小于雪花算法使用到的最大时间戳lastTimestamp
     * @return
     */
    public synchronized long nextId() {
        long timestamp = timeGen();
 
        if (timestamp < lastTimestamp) {
            //throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
            if(!clockMovedBackward.get()){
                log.warn("发现时钟回拨,启用单位时间内雪花序列递进策略");
                clockMovedBackward.compareAndSet(false,true);
            }
        }else{
            if(clockMovedBackward.get()){
                log.warn("时钟已恢复正常,恢复为正常策略");
                clockMovedBackward.compareAndSet(true,false);
            }
        }
 
        if (timestamp <= lastTimestamp) {
            //系统当前时间小于或等于lastTimestamp
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                //sequence + 1后达到最大序列数了,获取新的时间
                timestamp = tilNextMillis(lastTimestamp);
            }else{
                //未达到最大序列数,继续使用lastTimestamp的值
                timestamp = lastTimestamp;
            }
        } else {
            sequence = 0L;
        }
 
        lastTimestamp = timestamp;
 
        return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
    }
 
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        if(timestamp <= lastTimestamp){
            if(clockMovedBackward.get()){
                //时钟回拨了,返回时间戳+1,逐步递进时间戳
                return ++lastTimestamp;
            }else{
                //时钟是正常的,等到1秒后再获取一次当前时间
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //ignore
                }
                timestamp = timeGen();
                if (timestamp <= lastTimestamp) {
                    //重试获取当前时间还是小于等于lastTimestamp，只能返回时间戳+1
                    return ++lastTimestamp;
                }
            }
        }
        return timestamp;
    }

    protected long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * 获取新的id
     * @return
     */
    public static long getNextId() {
        return IDGENERATOR.nextId();
    }

    public static String getNextIdStr() {
        return IDGENERATOR.nextId() + "";
    }
    
}
